package uk.ac.glasgow.demosocs.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import uk.ac.glasgow.demosocs.Candidate;
import uk.ac.glasgow.demosocs.Election;
import uk.ac.glasgow.demosocs.ElectionState;
import uk.ac.glasgow.demosocs.Vote;
import uk.ac.glasgow.demosocs.VotingSystem;
import uk.ac.glasgow.demosocs.impl.electionTypes.Referendum;
import uk.ac.glasgow.demosocs.store.ElectionStore;
import uk.ac.glasgow.demosocs.store.ElectorateStore;
import uk.ac.glasgow.demosocs.store.ElectorateStoreImpl;
import uk.ac.glasgow.demosocs.store.UserStoreImpl;

import java.util.ArrayList;

public class VotingSystemStub implements VotingSystem {

	/**
	 * @author Bilal Hamid, Tsvetelina Valcheva
	 * @version 1.2
	 * 
	 *          This class realises the voting system facade. Users can also be
	 *          added or deleted from the user store here.
	 * 
	 * 
	 */

	private ElectionImpl currentElection;
	private UserStoreImpl userStore;
	private ElectionStore electionStore;
	private ElectorateStore electorateStore;
	private Set<ElectionImpl> currentElections = new HashSet<ElectionImpl>();
	private String currentUserGUID;

	/**
	 * This creates an instance of our voting system implementation. It takes
	 * the user store implementation as a parameter. Here you can hard code
	 * users, as required.
	 * 
	 * Also creates an instance of the election store that saves the election
	 * object to a file.
	 * 
	 * Finally it call a private method which, reads election objects from the
	 * election store and adds the election to the list containing all current
	 * elections.
	 * 
	 * And prints the menu.
	 * 
	 * @param users
	 */
	public VotingSystemStub() {
		userStore = new UserStoreImpl("users.txt");
		;
		userStore.addUser("", "b", "admin", "p");
		userStore.addUser("john", "doe", "1", "p");
		userStore.addUser("my", "cat", "2", "p");
		userStore.addUser("bob", "bobs_surname", "3", "p");
		userStore.addUser("jill", "jills_surname", "4", "p");

		printLoginMenu();
		electionStore = new ElectionStore("electionStore.txt");
		electorateStore = new ElectorateStoreImpl("electorateStore.txt");

		electorateStore.addElectorate("Level_3");
		electorateStore.addElectorate("Level_5");
		electorateStore.addUserToElectorate("Level_3", "1");
		electorateStore.addUserToElectorate("Level_3", "2");
		electorateStore.addUserToElectorate("Level_3", "3");
		electorateStore.addUserToElectorate("Level_5", "3");
		electorateStore.addUserToElectorate("Level_5", "4");
		electorateStore.addUserToElectorate("Level_5", "2");
		
//		/*test 100,000 votes*/
//		electorateStore.addElectorate("multipleTest");
//		
//		for (int i = 5; i <100006; i++){
//			userStore.addUser("a"+i,  Integer.toString(i), "a"+i, "p");
//			electorateStore.addUserToElectorate("multipleTest", Integer.toString(i));
//		}
//		//

		intialiaseCurrentElection();
	}

	/**
	 * Accepts the nomination in the currently selected election for the
	 * currently selected user.
	 */
	@Override
	public void acceptNomination() {
		String forename = userStore.getUser(currentUserGUID).getForename();
		String surname = userStore.getUser(currentUserGUID).getSurname();
		Candidate e = new CandidateImpl(forename, surname, currentUserGUID, currentElection.getEID());

		Set<Candidate> list = currentElection.getUnapprovedCandidates();

		if (list.contains(e)) {
			currentElection.getCandidates().add(e); // Add to the approved list
			list.remove(e); // Remove from unapproved list as they have accept
							// nomination
			electionStore.addElection(currentElection); // Save state to store.
		}

	}

	/**
	 * Casts the specified vote in the currently selected election for the
	 * currently selected user if: the user is a member of the electorate in the
	 * currently selected election.
	 * 
	 * @param casts
	 */
	@Override
	public void castVote(Vote vote) {
		// Checks if current user is eligible to cast a vote
		if (!electorateStore.isEligible(currentElection.getElectorateID(), currentUserGUID)) {
			System.out.println("You are not eligible to vote for election");
			return;
		}

		// Cast vote for simple plurality
		if (currentElection.getElectoralSystem().getName().equals("Simple_Plurality")) {

			OneOfNVoteStub oneOfVote = ((OneOfNVoteStub) vote);
			String candidateId = oneOfVote.getCandidateGUID();
			oneOfVote.setVotedBy(currentUserGUID);

			Candidate e = new CandidateImpl(candidateId);

			if (currentElection.getCandidates().contains(e)) {
				currentElection.getElectoralSystem().vote(oneOfVote);
				electionStore.addElection(currentElection);
				System.out.println("A vote was cast for [" + candidateId + "].");
				return;
			}

			System.out.println("A vote could not be cast for [" + candidateId + "], maybe they have" + " not been nominated?");

		}

		// Cast vote for an AV election.
		else if (currentElection.getElectoralSystem().getName().equals("Alternative_Vote")) {
			currentElection.getElectoralSystem().vote(vote);
			electionStore.addElection(currentElection);

		}

		else if (currentElection.getElectoralSystem().getName().equals("Referendum")) {
			ReferendumVoteImpl referendumVote = ((ReferendumVoteImpl) vote);
			referendumVote.setVotedBy(currentUserGUID);
			currentElection.getElectoralSystem().vote(referendumVote);
			electionStore.addElection(currentElection);

		}

	}

	/**
	 * Creates a new election of the specified name and type, if the currently
	 * selected user is an election manager. The newly created election is
	 * automatically set as the current election.
	 * 
	 * @param name
	 * @param typeID
	 */
	@Override
	public void createElection(String name, String typeID) {
		if (!isElectionManager()) {
			return;
		}

		int id = electionStore.getElections().size();
		ElectionImpl el;

		try {
			el = new ElectionImpl(name, typeID, id);
		} catch (InvalidElectorateSystem e) {
			System.out.println("Invalid electoral system, could not create election.");
			return;
		}
		currentElections.add(el);
		currentElection = el; // Assume created election is now the current
								// election.
		electionStore.addElection(currentElection);
		System.out.printf("Created election with name [" + name + "], type [" + typeID + "] ");

	}

	/**
	 * Sets the details for the currently selected election.
	 * 
	 * @param openNominations
	 *            the date from which candidates may be nominated until the
	 *            election is started.
	 * @param start
	 *            the date the election starts and votes may be cast
	 * @param end
	 *            the date the election ends and no more votes may be cast.
	 * @param electoratelectionID
	 *            the identifier for the group of voters eligible to participate
	 *            in the election.
	 */
	@Override
	public void editElection(Date openNominations, Date start, Date end, String electoratelectionID) {
		if (!isElectionManager() || currentElection == null) {
			return;
		}

		if (!electorateStore.isValidElectorate(electoratelectionID)) {
			System.out.println("Not a valid electorate type, please edit the election");
			return;
		}
		currentElection.editElection(openNominations, start, end, electoratelectionID);
		electionStore.addElection(currentElection);
		checkElectionState();
	}

	/**
	 * 
	 * @return the set of elections managed by the voting system.
	 */
	@Override
	public Set<Election> getElections() {
		Set<Election> allElections = new HashSet<Election>();
		for (Election e : currentElections) {
			allElections.add(e);
		}
		return allElections;
	}

	/**
	 * Nominates a candidate to stand in the currently selected election, if:
	 * there is a currently selected election for which nominations are open;
	 * the GUID is for a valid user who is a member of the electorate for the
	 * election; and the currently selected user is a member of the electorate
	 * for the currently specified election.
	 * 
	 * @param GUID
	 *            the Glasgow University Identifier for the proposed candidate.
	 */
	@Override
	public void nominateCandidate(String GUID) {
		String forename = "";
		String surname = "";
		String electorateName = currentElection.getElectorateID();
		System.out.println("Eligible voters: " +electorateName);

		// Check if user is not nominating themselves.
		if (GUID.equals(currentUserGUID)) {
			System.out.println("Can not nominate yourself");
			return;
		}
		
		if (currentElection.getElectoralSystem().getName().equals("Referendum")){
			System.out.println("Nominations in referendum's are not allowed");
			return;
		}

		// Checks if current user eligible to nominate for election
		if (!electorateStore.isEligible(electorateName, currentUserGUID)) {
			System.out.println("You are not eligible to vote for this election.");
			return;
		}

		// Checks if nominate person is in the electorate set for the election.
		if (!electorateStore.isEligible(electorateName, GUID)) {
			System.out.println("This candidate is not eligible to be nominated for this election");
			return;
		}

		try {
			forename = userStore.getUser(GUID).getForename();
			surname = userStore.getUser(GUID).getSurname();
		} catch (Exception e) {
			System.out.println("No such user");

		}

		// Assume a valid candidate has now been found from the user store.
		Candidate e = new CandidateImpl(forename, surname, GUID, currentElection.getEID());

		if (!currentElection.getUnapprovedCandidates().contains(e)) {
			currentElection.getUnapprovedCandidates().add(e);
			electionStore.addElection(currentElection);
		}

	}

	/**
	 * Publishes the results of the currently selected election, if the election
	 * has ended and the currently selected user is the election manager for the
	 * currently selected election.
	 */
	@Override
	public void publishResults() {
		if (!isElectionManager()) {
			return;
		}
		currentElection.getElectionResult();
		currentElection.setElectionState(ElectionState.PUBLISHED);
		electionStore.addElection(currentElection);
	}

	/**
	 * Sets the current election with the election id specified.
	 */
	@Override
	public void setCurrentElection(String electionID) {
		for (ElectionImpl el : currentElections) {
			if (el.getEID().equals(electionID)) {
				currentElection = el;
				checkElectionState();
				if (el.getElectoralSystem().getName().equals("Referendum"))
					System.out.println("Question: "+((Referendum) el.getElectoralSystem()).getQuestion());
			}
		}
	}

	/**
	 * Sets the current user with the guid specified.
	 * 
	 * @param GUID
	 *            The guid of the user
	 * @param password
	 *            The password of the user
	 */
	@Override
	public boolean setCurrentUser(String GUID, String password) {
		if (userStore.getUser(GUID, password) != null) {
			printMenu();
			currentUserGUID = GUID;
			return true;
		} else
			return false;
	}

	/**
	 * 
	 * @return The set of elections for which the currently selected user has
	 *         been nominated.
	 * 
	 */
	@Override
	public Set<Election> getNominations() {
		Set<Election> nominatedElections = new HashSet<Election>();
		for (Election e : currentElections) {
			nominatedElections.add(e);
		}
		return nominatedElections;
	}

	@Override
	public Election getCurrentElection() {
		return currentElection;
	}

	@Override
	public String getCurrentUserGUID() {
		return currentUserGUID;
	}

	/**
	 * This method sets the election state. Depending on the start and end dates
	 * for an election, this method will either set the election state to
	 * started or ended.
	 * 
	 */
	private void checkElectionState() {
		Date currentDate = new Date();

		// end day < current day so election has finished.
		if (currentElection.getEnd().before(currentDate))
			currentElection.setElectionState(ElectionState.ENDED);

		// start day < current day - so election is running.
		else if (currentElection.getStart().before(currentDate))
			currentElection.setElectionState(ElectionState.STARTED);
	}

	/**
	 * Prints initial login menu
	 */
	private static void printLoginMenu() {
		System.out.println("DemoSocs Voting System");
		System.out.println("=============================");
		System.out.println("=============================\n");

		System.out.println("Option - [Usage]\n");
		System.out.println("login: user [matric] [password]");
		System.out.println("help \n\n");
	}

	/**
	 * Prints menu once the user has logged in.
	 */
	private static void printMenu() {
		System.out.println("=============================");
		System.out.println("=============================\n");
		System.out.println("DemoSocs Voting System");
		System.out.println("=============================");
		System.out.println("=============================\n");
		System.out.println("Successfully logged in\n");
		System.out.println("Option - [Usage]\n");
		System.out.println("user [matric] [password]");

		System.out.println("election [election id]");
		System.out.println("create [election name] [type] [nomination day] [nomination time] [start day] [start time] [stop time] [electorate id]");
		System.out.println("edit [open nomination date] [nomination time] [election start date] [start time] [stop day] [stop time] [electorate ID]");
		System.out.println("elections");
		System.out.println("nominate [candidate name]");
		System.out.println("accept");
		System.out.println("nominations");
		System.out.println("cast [candidate]");
		System.out.println("publish");
		System.out.println("candidates");
		System.out.println("electorate");
		System.out.println("stop");
		System.out.println("help \n");
	}

	/**
	 * This method reads from a file containing election objects. As a result
	 * the election state remains consistent every time the program is run.
	 * 
	 */

	private void intialiaseCurrentElection() {
		if (!electionStore.getElections().isEmpty()) {
			Iterator iter = electionStore.getElections().keySet().iterator();

			while (iter.hasNext()) {
				String eid = (String) iter.next();
				currentElection = electionStore.getElections().get(eid);
				checkElectionState();
				currentElections.add(currentElection);
			}
			currentElection = null;
		}
	}

	private boolean isElectionManager() {
		if (!currentUserGUID.equals("admin")) {
			System.out.println("Admin access required");
			return false;
		}
		return true;
	}

}